/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package org.butenko.celluar;

import java.util.ArrayList;
import java.util.List;

/**
 *
 * @author gbu.000
 */
public class Field {
    private static final int COORDINATE_SPAN_PER_BLOCK = 10;

    private final int dimentions;
    private final BlockList rootBlock = new RegularBlockList();

    public Field(int dimentions) {
        this.dimentions = dimentions;
    }

    private DataBlock getDataBlock(int... coordinates) {
        assert dimentions == coordinates.length;
        final int dataBlockSize = COORDINATE_SPAN_PER_BLOCK ^ dimentions;
        BlockList currentBlock = rootBlock;
        for (int ci = 0; ci < coordinates.length; ci++) {
            final int i = coordinateToBlockIndex(coordinates[ci]);
            BlockList nextBlock = currentBlock.get(i);
            if (nextBlock == null) {
                if (ci < coordinates.length - 1) {
                    nextBlock = new RegularBlockList();
                } else {
                    nextBlock = new DataBlock(dataBlockSize);
                }
                currentBlock.put(ci, nextBlock);
            }
            currentBlock = nextBlock;
            assert (currentBlock instanceof DataBlock && ci == coordinates.length - 1)
                    || (!(currentBlock instanceof DataBlock) && ci < coordinates.length - 1);
            return (DataBlock) currentBlock;
        }
        throw new AssertionError();//should not reach here
    }

    private int coordinatesToBlockDataOffset(int... coordinates) {
        int index = 0;
        for (int i = 0; i < coordinates.length; i++) {
            final int localOffset = coordinates[i] % COORDINATE_SPAN_PER_BLOCK;
            index += localOffset * COORDINATE_SPAN_PER_BLOCK ^ i;
        }
        return index - 1;
    }

    private int coordinateToBlockIndex(int coordinate) {
        return coordinate / COORDINATE_SPAN_PER_BLOCK;
    }

    public boolean get(int... coordinates) {
        return getDataBlock(coordinates).getData()[coordinatesToBlockDataOffset(coordinates)];
    }

    public void set(boolean value, int... coordinates) {
        getDataBlock(coordinates).getData()[coordinatesToBlockDataOffset(coordinates)] = value;
    }

    private interface BlockList {

        BlockList get(int blockIndex);

        void put(int blockIndex, BlockList child);
    }

    private static class RegularBlockList implements BlockList {

        private final List<BlockList> lists = new ArrayList<>();

        @Override
        public BlockList get(int blockIndex) {
            if (blockIndex >= 0 && blockIndex < lists.size()) {
                return lists.get(blockIndex);
            }
            return null;
        }

        @Override
        public void put(int blockIndex, BlockList child) {
            // array list isn't sparce.
            // we have to fill it up with nulls to expand range
            // of valid indices
            for (int i = lists.size(); i < blockIndex; i++) {
                lists.add(null);
            }
            lists.set(blockIndex, child);
        }
    }

    private static class DataBlock implements BlockList {

        public boolean[] data;
        private final int dataSize;
        
        public DataBlock(int dataSize) {
            this.dataSize = dataSize;
        }

        public boolean[] getData() {
            if (data == null) {
                data = new boolean[dataSize];
            }
            return data;
        }

        @Override
        public BlockList get(int blockIndex) {
            return null;
        }

        @Override
        public void put(int blockIndex, BlockList child) {
            throw new UnsupportedOperationException("Not supported");
        }
    }
}
